package cn.virens.common.mapper.example.mapper;

import cn.virens.common.exception.APIException;
import cn.virens.common.mapper.example.base.AbstractExampleCriteria;
import cn.virens.common.mapper.example.util.SQLBuidler;
import cn.virens.common.mapper.example.util.Util;
import cn.virens.common.utils.StreamUtil;
import io.mybatis.provider.EntityTable;
import io.mybatis.provider.SqlScript;
import org.apache.ibatis.builder.annotation.ProviderContext;

import java.util.stream.Collectors;

public class ExampleProvider {

    /**
     * 根据 Example 删除
     *
     * @param providerContext 上下文
     * @return cacheKey
     */
    public static String deleteByExample(ProviderContext providerContext) {
        return SqlScript.caching(providerContext, (entity, util) -> {
            SQLBuidler buidler = SQLBuidler.of("DELETE FROM ", entity.table());

            buidler.assertNotNull("example.criteriaList");

            return buildCriteria(entity, buidler).toString();
        });
    }

    /**
     * 根据 Example 条件批量更新实体信息
     *
     * @param providerContext 上下文
     * @return cacheKey
     */
    public static String updateByExample(ProviderContext providerContext) {
        return SqlScript.caching(providerContext, (entity, util) -> {
            SQLBuidler buidler = SQLBuidler.of("UPDATE ", entity.table());

            buidler.assertNotNull("example.criteriaList");

            buildUpdateColumns(entity, buidler);
            buildCriteria(entity, buidler);

            return buidler.toString();
        });
    }

    /**
     * 根据 Example 条件批量更新实体不为空的字段
     *
     * @param providerContext 上下文
     * @return cacheKey
     */
    public static String updateByExampleSelective(ProviderContext providerContext) {
        return SqlScript.caching(providerContext, (entity, util) -> {
            SQLBuidler buidler = SQLBuidler.of("UPDATE ", entity.table());

            buidler.assertNotNull("example.criteriaList");

            buildUpdateSelective(entity, buidler);
            buildCriteria(entity, buidler);

            return buidler.toString();
        });
    }

    /**
     * 根据 Example 条件批量查询，根据 Example 条件查询总数，查询结果的数量由方法定义
     *
     * @param providerContext 上下文
     * @return cacheKey
     */
    public static String selectByExample(ProviderContext providerContext) {
        return SqlScript.caching(providerContext, (entity, util) -> {
            SQLBuidler buidler = SQLBuidler.of("SELECT ");

            buildSelectColumns0(entity, buidler);
            buildSelectColumns1(entity, buidler);

            buidler.appendLE("FROM ", entity.table());

            buildJoinTable(entity, buidler);
            buildCriteria(entity, buidler);

            buildGroupBy(entity, buidler);
            buildGaving(entity, buidler);
            buildOrder(entity, buidler);
            buildLimit(entity, buidler);

            return buidler.toString();
        });
    }

    /**
     * 根据 Example 条件批量查询，根据 Example 条件查询总数，查询结果的数量由方法定义
     *
     * @param providerContext 上下文
     * @return cacheKey
     */
    public static String selectCountByExample(ProviderContext providerContext) {
        return SqlScript.caching(providerContext, (entity, util) -> {
            SQLBuidler buidler = SQLBuidler.of("SELECT COUNT(1) ");
            buidler.appendLE("FROM ", entity.table());

            buildJoinTable(entity, buidler);
            buildCriteria(entity, buidler);

            buildGroupBy(entity, buidler);
            buildGaving(entity, buidler);
            buildOrder(entity, buidler);
            buildLimit(entity, buidler);

            return buidler.toString();
        });
    }

    private static void buildSelectColumns0(EntityTable entity, SQLBuidler buidler) throws APIException {
        buidler.ifNotEmpty("example.selectColumns", a -> a.foreach("example.selectColumns", "c", ",", c -> c.append("${c}")));
    }

    /**
     * 如果没有自定义待查询字段，就获取对象中现成的字段
     */
    private static void buildSelectColumns1(EntityTable entity, SQLBuidler buidler) throws APIException {
        buidler.ifEmpty("example.selectColumns", a -> a.append(entity.selectColumns().stream().map(z -> {
            return entity.table() + "." + (entity.useResultMaps() ? z.column() : z.columnAsProperty());
        }).collect(Collectors.joining(","))));
    }

    private static SQLBuidler buildLimit(EntityTable entity, SQLBuidler buidler) throws APIException {
        return buidler.ifNotEmpty("example.limitByClause", a -> a.append("LIMIT ${example.limitByClause}"));
    }

    private static SQLBuidler buildOrder(EntityTable entity, SQLBuidler buidler) throws APIException {
        return buidler.ifNotEmpty("example.orderByClause", a -> a.append("ORDER BY ${example.orderByClause}"));
    }

    private static SQLBuidler buildCriteria(EntityTable entity, SQLBuidler buidler) throws APIException {
        return buidler.trim("WHERE", Util.TRIM, a -> a.foreach("example.criteriaList", "citem", b -> {
            b.trim("${citem.andOr}\n", "(", ")", Util.TRIM, "", c -> condition(c, "citem.criterionList"));
        }));
    }

    private static SQLBuidler buildGaving(EntityTable entity, SQLBuidler buidler) throws APIException {
        return buidler.trim("HAVING ", Util.TRIM, a -> condition(a, "example.havingList"));
    }

    private static SQLBuidler buildGroupBy(EntityTable entity, SQLBuidler buidler) throws APIException {
        return buidler.ifNotEmpty("example.groupByClause", a -> a.append("GROUP BY ${example.groupByClause}"));
    }

    private static SQLBuidler buildJoinTable(EntityTable entity, SQLBuidler buidler) throws APIException {
        return buidler.foreach("example.joinList", "joinTable", a -> {
            a.trim("${joinTable.join} JOIN ${joinTable.table} \n", "ON ", Util.TRIM, b -> {
                condition(b, "joinTable.criterionList");
            });
        });
    }

    private static void buildUpdateColumns(EntityTable entity, SQLBuidler buidler) throws APIException {
        buidler.set(",", () -> StreamUtil.stream(entity.updateColumns()).map(z -> {
            return z.columnEqualsProperty("entity.");
        }));
    }

    private static void buildUpdateSelective(EntityTable entity, SQLBuidler buidler) throws APIException {
        buidler.set("", () -> StreamUtil.stream(entity.updateColumns()).map(z -> {
            return SQLBuidler.of().ifTest(z.notNullTest("entity."), a -> {
                a.append(z.columnEqualsProperty("entity."), ",");
            }).toString();
        }));
    }

    /**
     * {@link AbstractExampleCriteria.TYPE_NO}<br>
     * {@link AbstractExampleCriteria.TYPE_LIST}<br>
     * {@link AbstractExampleCriteria.TYPE_SINGLE}<br>
     * {@link AbstractExampleCriteria.TYPE_BETWEEN}<br>
     */
    private static SQLBuidler condition(SQLBuidler buidler, String criterionList) throws APIException {
        return buidler.foreach(criterionList, "item", a -> a.choose((b) -> {
            b.chooseWhen("item.type == 0", c -> c.append("\n${item.andOr} (${item.condition})"));
            b.chooseWhen("item.type == 2", c -> c.append("\n${item.andOr} (${item.condition} #{item.value1})"));
            b.chooseWhen("item.type == 3", c -> c.append("\n${item.andOr} (${item.condition} #{item.value1} and #{item.value2})"));
            b.chooseWhen("item.type == 1", c -> c.append("\n${item.andOr} ${item.condition} ").foreach("item.value1", "v", ",", "(", ")", d -> d.append("#{v}")));
        }));
    }

}
